import asyncio
import socket
from asyncio import AbstractEventLoop
from PyCmpltrtok.common import get_now_string_short, sep
import logging

HOST="0.0.0.0"
PORT=8000

async def echo(connection: socket,
               loop: AbstractEventLoop) -> None:
    try:
        while data := await loop.sock_recv(connection, 1024):
            dt = get_now_string_short()
            print(f'{dt}: Got {data} from {connection}')
            if data == b'boom\r\n':
                raise Exception(f"The artificial 'boom' error from {connection}")
            await loop.sock_sendall(connection, f'{dt}: {data}\n'.encode('utf8'))
    except Exception as ex:
        logging.exception(f"{dt}: {ex}")
        raise ex  # it seems that if all exceptions are handled, the task is garbage collected at once
    finally:
        connection.close()

"""
asyncio can only print
 this message and the traceback for a failed task when that task is garbage collected.
 This is because it has no way to tell if that task will be awaited at some other point in
 the application and would therefore raise an exception then. 
"""
tasks = []


async def listen_for_connection(server_socket: socket,
                                loop: AbstractEventLoop):
    while True:
        connection, address = await loop.sock_accept(server_socket)
        connection.setblocking(False)
        print(f"Got a connection from {address}")
        r = asyncio.create_task(echo(connection, loop))
        print('**** result:', r)
        tasks.append(r)


async def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    server_address = (HOST, PORT)
    server_socket.setblocking(False)
    server_socket.bind(server_address)
    server_socket.listen()

    print(f'Listening {HOST}:{PORT}')
    await listen_for_connection(server_socket, asyncio.get_event_loop())

asyncio.run(main())

print('**** tasks:', tasks)
sep('All over')
